package com.lz.aspect;

import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import com.alibaba.fastjson.JSON;
import com.lz.model.WebLog;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.annotation.Order;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * @Package com.lz.aspect
 * @Project currency-trading
 * @Author lenovo
 * @Date 2022/1/10 9:57
 * @Describe web层日志切面记录
 * @Version V1.0
 */

@Component
//设置为切面类
@Aspect
//切面顺序
@Order(1)
//@Slf4j
public class WebLogAspect {

    private static Logger LOGGER = LoggerFactory.getLogger(WebLogAspect.class);
    /**
     * 日志记录：
     * 环绕通知 -→ 方法执行之前、之后
     */

    /**
     * 1、定义切入点
     */
    // controller包里面所有类，类里面的所有方法都有该切面
    @Pointcut("execution(* com.lz.controller.*.*(..))") //类.方法(所有形参)

    public void webLog() {
    }

    /**
     * 2、记录日志的环绕通知
     */
    //对webLog()切入点进行环绕通知
    @Around("webLog()")
    public Object recodeWebLog(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Object result = null;
        WebLog webLog = new WebLog();
        long startTime = System.currentTimeMillis();

        // 执行方法的真实调用
        result = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
        long endTime = System.currentTimeMillis();
        // 设置请求该接口花费的时间
        webLog.setSpendTime((int) (startTime - endTime) / 1000);

        // 获取当前请求的request对象
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        // 获取安全的上下文
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

        String url = request.getRequestURL().toString();
        // 设置请求的uri
        webLog.setUri(request.getRequestURI());
        // 设置请求的url
        webLog.setUrl(url);
        // http://ip:port/
        webLog.setBasePath(StrUtil.removeSuffix(url, URLUtil.url(url).getPath()));
        // 设置用户名
        webLog.setUsername(authentication == null ? "anonymous" : authentication.getPrincipal().toString());
        // 设置请求的ip
        webLog.setIp(request.getRemoteAddr());

        // 获取方法签名
        MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
        // 获取类的名称
        String targetClassName = proceedingJoinPoint.getTarget().getClass().getName();
        Method method = signature.getMethod();
        // 因为会使用Swagger工具，必须在方法上面添加@ApiOperation(value="")注解获取ApiOperation
        ApiOperation annotation = method.getAnnotation(ApiOperation.class);
        // 设置描述信息
        webLog.setDescription(annotation == null ? "no desc" : annotation.value());
        // com.lz.controller.UserController.login()
        // 设置类名.方法名
        webLog.setMethod(targetClassName + "." + method.getName());
        // {"key_参数的名称":"value_参数的值"}
        // 设置参数
        webLog.setParameter(getMethodParameter(method, proceedingJoinPoint.getArgs()));
        // 设置结果
        webLog.setResult(result);
        LOGGER.info(JSON.toJSONString(webLog, true));
        return result;
    }

    /**
     * 获取方法的执行参数
     *
     * @param method
     * @param args
     * @return {"key_参数的名称":"value_参数的值"}
     */
    private Object getMethodParameter(Method method, Object[] args) {
        Map<String, Object> methodParametersWithValues = new HashMap<>();
        LocalVariableTableParameterNameDiscoverer localVariableTableParameterNameDiscoverer =
                new LocalVariableTableParameterNameDiscoverer();
        // 方法的形参名称
        String[] parameterNames = localVariableTableParameterNameDiscoverer.getParameterNames(method);
        for (int i = 0; i < parameterNames.length; i++) {
            if (parameterNames[i].equals("password") || parameterNames[i].equals("file")) {
                methodParametersWithValues.put(parameterNames[i], "受限的支持类型");
            } else {
                methodParametersWithValues.put(parameterNames[i], args[i]);
            }

        }

        return methodParametersWithValues;
    }
}
